const fs = require('fs');
const web3 = require('./web3');
const utils = require('./utils');

const GAS_LIMIT = 20_000_000; // 20 million

const loadContract = (() => {
  let IPContract = null;
  let BurnContract = null;
  let TransferContractInner = null;
  let TransferContract = null;
  let ZetherContract = null;

  const pp = `${__dirname}/../contracts`;

  return ((contract_name) => {
    if (!contract_name.localeCompare('IPContract')) {
      if (!IPContract) {
        const n = `${pp}/InnerProductVerifier_sol_InnerProductVerifier`;
        IPContract = {
          abi: JSON.parse(fs.readFileSync(`${n}.abi`)),
          bin: fs.readFileSync(`${n}.bin`).toString()
        };
      }
      return IPContract;
    } else if (!contract_name.localeCompare('BurnContract')) {
      if (!BurnContract) {
        const n = `${pp}/BurnVerifier_sol_BurnVerifier`;
        BurnContract = {
          abi: JSON.parse(fs.readFileSync(`${n}.abi`)),
          bin: fs.readFileSync(`${n}.bin`).toString()
        };
      }
      return BurnContract;
    } else if (!contract_name.localeCompare('TransferContractInner')) {
      const n = `${pp}/TransferVerifierInner_sol_TransferVerifierInner`;
      if (!TransferContractInner) {
        TransferContractInner = {
          abi: JSON.parse(fs.readFileSync(`${n}.abi`)),
          bin: fs.readFileSync(`${n}.bin`).toString()
        };
      }
      return TransferContractInner;
    } else if (!contract_name.localeCompare('TransferContract')) {
      if (!TransferContract) {
        const n = `${pp}/TransferVerifier_sol_TransferVerifier`;
        TransferContract = {
          abi: JSON.parse(fs.readFileSync(`${n}.abi`)),
          bin: fs.readFileSync(`${n}.bin`).toString()
        };
      }
      return TransferContract;
    } else if (!contract_name.localeCompare('ZetherContract')) {
      if (!ZetherContract) {
        const n = `${pp}/Zether_sol_Zether`;
        ZetherContract = {
          abi: JSON.parse(fs.readFileSync(`${n}.abi`)),
          bin: fs.readFileSync(`${n}.bin`).toString()
        };
      }
      return ZetherContract;
    }

    throw Error(`error contract: ${contract_name}`);
  });
})();

module.exports = exports = {};

exports.deployInnerProductVerifier = function deployInnerProductVerifier(ethacc) {
  const IPContract = loadContract('IPContract');
  const ipcv = new web3.eth.Contract(IPContract.abi);
  return new Promise((resolve, reject) => {
    ipcv.deploy({
      data: IPContract.bin,
      arguments: [],
    }).send({
      from: ethacc,
      gas: GAS_LIMIT,
    }).on('receipt', (receipt) => {
      resolve(receipt);
    }).on('error', (err) => {
      reject(err);
    });
  });
};

exports.deployBurnVerifier = function deployBurnVerifier(ipacc, ethacc) {
  const BurnContract = loadContract('BurnContract');
  const bv = new web3.eth.Contract(BurnContract.abi);
  return new Promise((resolve, reject) => {
    bv.deploy({
      data: BurnContract.bin,
      arguments: [ipacc],
    }).send({
      from: ethacc,
      gas: GAS_LIMIT,
    }).on('receipt', (receipt) => {
      resolve(receipt);
    }).on('error', (err) => {
      reject(err);
    });
  });
};

exports.deployTransferVerifierInner = function deployTransferVerifierInner(ethacc) {
  const TransferContractInner = loadContract('TransferContractInner');
  const tv = new web3.eth.Contract(TransferContractInner.abi);
  return new Promise((resolve, reject) => {
    tv.deploy({
      data: TransferContractInner.bin,
      arguments: [],
    }).send({
      from: ethacc,
      gas: GAS_LIMIT,
    }).on('receipt', (receipt) => {
      resolve(receipt);
    }).on('error', (err) => {
      reject(err);
    });
  });
};

exports.deployTransferVerifier = function deployTransferVerifier(ip, tvi, ethacc) {
  const TransferContract = loadContract('TransferContract');
  const tv = new web3.eth.Contract(TransferContract.abi);
  return new Promise((resolve, reject) => {
    tv.deploy({
      data: TransferContract.bin,
      arguments: [ip, tvi],
    }).send({
      from: ethacc,
      gas: GAS_LIMIT,
    }).on('receipt', (receipt) => {
      resolve(receipt);
    }).on('error', (err) => {
      reject(err);
    });
  });
};

exports.deployZether = function deployZether(epoch_len, bv, tv, ethacc) {
  const ZetherContract = loadContract('ZetherContract');
  const zc = new web3.eth.Contract(ZetherContract.abi);
  return new Promise((resolve, reject) => {
    zc.deploy({
      data: ZetherContract.bin,
      arguments: [epoch_len, bv, tv],
    }).send({
      from: ethacc,
      gas: GAS_LIMIT,
    }).on("receipt", (receipt) => {
      resolve(receipt);
    }).on("error", (error) => {
      reject(error);
    });
  });
};

exports.register = function register(keypair, contract_address, ethacc) {
  const ZetherContract = loadContract('ZetherContract');
  const zc = new web3.eth.Contract(ZetherContract.abi, contract_address);
  const [c, s] = utils.sign(contract_address, keypair);
  return zc.methods.register(keypair.y, c, s).send({ from: ethacc, gas: GAS_LIMIT});
};

exports.register_sim = function register_sim(keypair, contract_address) {
  const ZetherContract = loadContract('ZetherContract');
  const zc = new web3.eth.Contract(ZetherContract.abi, contract_address);
  const [c, s] = utils.sign(contract_address, keypair);
  return zc.methods.register(keypair.y, c, s).call();
};

exports.info_burn = function info_burn(contract_address) {
  const BurnContract = loadContract('BurnContract');
  const c = new web3.eth.Contract(BurnContract.abi, contract_address);
  return c.methods.info().call();
};

exports.info_transfer = function info_transfer(contract_address) {
  const TransferContract = loadContract('TransferContract');
  const c = new web3.eth.Contract(TransferContract.abi, contract_address);
  return c.methods.info().call();
};

exports.info_zether = function info(contract_address) {
  const ZetherContract = loadContract('ZetherContract');
  const zc = new web3.eth.Contract(ZetherContract.abi, contract_address);
  return zc.methods.info().call();
};

exports.balance = function balance(keypair, contract_address, epoch = 2**32) {
  const ZetherContract = loadContract('ZetherContract');
  const zc = new web3.eth.Contract(ZetherContract.abi, contract_address);
  return zc.methods.simulateAccounts([keypair.y], epoch).call()
    .then((res) => {
      return res[0];
    });
};

exports.balances = function balances(keypairs, contract_address, epoch = 2**32) {
  const ZetherContract = loadContract('ZetherContract');
  const zc = new web3.eth.Contract(ZetherContract.abi, contract_address);
  return zc.methods.simulateAccounts(keypairs, epoch).call();
};

exports.bare = function bare(keypair, contract_address) {
  const ZetherContract = loadContract('ZetherContract');
  const zc = new web3.eth.Contract(ZetherContract.abi, contract_address);
  return zc.methods.bare(keypair.y).call();
};


exports.fund = function func(keypair, b_in, contract_address, ethacc) {
  const ZetherContract = loadContract('ZetherContract');
  const zc = new web3.eth.Contract(ZetherContract.abi, contract_address);
  return zc.methods.fund(keypair.y, b_in).send({ from: ethacc, gas: GAS_LIMIT });
};

exports.fund_sim = function fund(keypair, b_in, contract_address, ethacc) {
  const ZetherContract = loadContract('ZetherContract');
  const zc = new web3.eth.Contract(ZetherContract.abi, contract_address);
  return zc.methods.fund(keypair.y, b_in).call({ from: ethacc });
};

exports.burn = function burn(keypair, b_out, u, proof, contract_address, ethacc) {
  const ZetherContract = loadContract('ZetherContract');
  web3.eth.handleRevert = true;

  const zc = new web3.eth.Contract(ZetherContract.abi, contract_address);
  return zc.methods
    .burn(keypair['y'], b_out, u, proof)
    .send({ from: ethacc, gas: GAS_LIMIT });
};

exports.burn_sim = function burn_sim(keypair, b_out, u, proof, contract_address, ethacc) {
  const ZetherContract = loadContract('ZetherContract');
  web3.eth.handleRevert = true;

  const zc = new web3.eth.Contract(ZetherContract.abi, contract_address);
  return zc.methods
    .burn(keypair['y'], b_out, u, proof)
    .call({ from: ethacc, gas: GAS_LIMIT });
};

exports.transfer = function transfer(C, D, y, u, proof, contract_address, ethacc) {
  const ZetherContract = loadContract('ZetherContract');
  web3.eth.handleRevert = true;

  const zc = new web3.eth.Contract(ZetherContract.abi, contract_address);
  return zc.methods
    .transfer(C, D, y, u, proof)
    .send({ from: ethacc, gas: GAS_LIMIT });
};

/*
exports.calcYHash = function calcYHash(y, contract_address) {
  const zether_contract = new web3.eth.Contract(ZetherContract.abi, contract_address);
  return zether_contract.methods.calcyhash(y).call();
};
*/
